package meta

import (
	"log"
	"time"
)

type MultipartUpload struct {
	Bucket     string `gorm:"not null"`
	Key        string `gorm:"not null"`
	UploadId   string `gorm:"primary_key"`
	Encryption string
	PartTotal  int64
	Date       string
	UserMeta   string
	Permission string
}

type UploadPart struct {
	UploadId     string `gorm:"not null"`
	BucketName   string `gorm:"not null"`
	PartName     string `gorm:"not null"`
	PartNumber   int64  `gorm:"not null"`
	ETag         string `gorm:"not null"`
	Size         int64  `gorm:"not null"`
	FileLocation string
	LastModified string
}

func InitMultipartUpload() {
	if !dbHook.HasTable(&MultipartUpload{}) {
		dbHook.CreateTable(&MultipartUpload{})
	}
}

func AddMultipartUpload(bucketName, key, uploadId, encryption, userMeta, permission string) (*MultipartUpload, bool) {
	var mu MultipartUpload
	_, ok := FindMultipartUpload(uploadId)
	if ok {
		return nil, false // cannot duplicate
	}
	mu = MultipartUpload{
		Bucket:     bucketName,
		Key:        key,
		UploadId:   uploadId,
		Encryption: encryption,
		UserMeta:   userMeta,
		Permission: permission,
		PartTotal:  0,
		Date:       time.Now().Format(time.RFC3339)}
	resp := dbHook.Create(&mu)

	if resp.Error != nil {
		log.Println(resp.Error.Error())
	}
	return &mu, true
}

func FindMultipartUpload(uploadId string) (*MultipartUpload, bool) {
	var mu MultipartUpload
	resp := dbHook.Find(&mu, "upload_id = ?", uploadId)
	if resp.RecordNotFound() {
		return nil, false
	} else if resp.Error != nil {
		log.Println(resp.Error.Error())
	}
	return &mu, true
}

func ListMultipartUpload(bucketName, prefix string) []MultipartUpload {
	var muList []MultipartUpload
	resp := dbHook.Where("bucket = ? and key like ?", bucketName, prefix+"%").Find(&muList)
	if resp.RecordNotFound() {
		return nil
	} else if resp.Error != nil {
		log.Println(resp.Error.Error())
	}

	return muList
}

func IncreaseMultipartUploadParts(uploadId string) bool {
	mu, ok := FindMultipartUpload(uploadId)
	if !ok {
		return false
	}
	dbHook.Model(mu).Update("part_total", mu.PartTotal+1)
	return true
}

func DeleteMultipartUpload(uploadId string) bool {
	var mu MultipartUpload
	resp := dbHook.Delete(&mu, "upload_id = ?", uploadId)
	if resp.RecordNotFound() {
		return false
	} else if resp.Error != nil {
		log.Println(resp.Error.Error())
	}
	return true
}

func InitUploadPartTable() {
	if !dbHook.HasTable(&UploadPart{}) {
		dbHook.CreateTable(&UploadPart{})
	}
}

func SetUploadPart(uploadId, bucketName, partName string, partNumber int64, tag string, size int64, fileLocation string, lastModified string) (*UploadPart, bool) {
	var uploadPart UploadPart
	entry, ok := GetUploadPart(uploadId, partNumber)
	if ok {
		entry.ETag = tag
		entry.Size = size
		entry.FileLocation = fileLocation
		entry.LastModified = lastModified
		dbHook.Save(entry)
		uploadPart = *entry
	} else {
		resp := dbHook.Create(&UploadPart{
			UploadId:     uploadId,
			BucketName:   bucketName,
			PartName:     partName,
			PartNumber:   partNumber,
			ETag:         tag,
			Size:         size,
			FileLocation: fileLocation,
			LastModified: lastModified})
		if resp.Error != nil {
			log.Println(resp.Error.Error())
		}
	}
	return &uploadPart, true
}

func GetUploadPart(uploadId string, partNumber int64) (*UploadPart, bool) {
	var uploadPart UploadPart
	resp := dbHook.Find(&uploadPart, "upload_id = ? and part_number = ?", uploadId, partNumber)
	if resp.RecordNotFound() {
		return nil, false
	} else if resp.Error != nil {
		log.Println(resp.Error.Error())
	}
	return &uploadPart, true
}

func ListUploadPart(uploadId string, marker int64) []UploadPart {
	var uploadPartList []UploadPart
	resp := dbHook.Where("upload_id = ? and part_number > ? ", uploadId, marker).Order("part_number").Find(&uploadPartList)
	if resp.RecordNotFound() {
		return nil
	}
	return uploadPartList
}

func DeleteUpload(uploadId string) bool {
	var uploadPart []UploadPart

	resp := dbHook.Where("upload_id = ?", uploadId).Delete(&uploadPart)
	if resp.RecordNotFound() {
		return false
	} else if resp.Error != nil {
		log.Println(resp.Error.Error())
	}
	return true
}
